package com.rxst.msg.excel;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.rxst.msg.dto.excel.MsgxUserExcelDto;
import com.rxst.msg.dto.excel.MsgxUserExcelErrorDto;
import com.rxst.msg.entity.MsgxUserEntity;
import com.rxst.msg.enums.constant.UploadConstant;
import com.rxst.msg.server.MsgxUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.DigestUtils;

import java.util.*;

/**
 * 字典表
 *
 * @author rxst
 * @email rxst@gmail.com
 * @date 2021-05-06 11:01:27
 */
@Slf4j
public class MsgxUserListener extends AnalysisEventListener<MsgxUserExcelDto> {

    private static final long serialVersionUID = 1L;

    /**
     * 每隔5条存储数据库，实际使用中可以3000条，然后清理list ，方便内存回收
     */
    private int batchCount = 3000;
    private Date date;
    /**
     * 导入数据
     */
    private List<MsgxUserEntity> list = new ArrayList<>();
    private List<String> user = new ArrayList<>();
    /**
     * 错误数据
     */
    private List<MsgxUserExcelDto> errorList = new ArrayList<>();

    private MsgxUserService msgxUserService;

    public MsgxUserListener() {
    }

    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     * @param msgxUserService
     */
    public MsgxUserListener(MsgxUserService msgxUserService) {
        this.msgxUserService = msgxUserService;
        this.date = DateUtil.date();
        checkList();
    }

    public MsgxUserListener(MsgxUserService msgxUserService, Integer batchCount) {
        this.msgxUserService = msgxUserService;
        this.batchCount = batchCount;
        this.date = DateUtil.date();
        checkList();
    }

    public void checkList() {
        List<MsgxUserEntity> list = msgxUserService.list();
        for (MsgxUserEntity entity : list) {
            if (!user.contains(entity.getUserAccount())) {
                user.add(entity.getUserAccount());
            }
        }
    }

    /**
     * 这个每一条数据解析都会来调用
     * 此处可以做值校验
     *
     * @param entity
     * @param context
     */
    @Override
    public void invoke(MsgxUserExcelDto entity, AnalysisContext context) {
        log.info("解析到一条数据:{}", JSONUtil.toJsonStr(entity));
        if (StrUtil.isBlank(entity.getUserName())) {
            setErrorMsg(entity, "用户名称不能为空");
        }
        if (StrUtil.isBlank(entity.getUserAccount())) {
            setErrorMsg(entity, "用户账号不能为空");
        }
        if (StrUtil.isBlank(entity.getUserPassword())) {
            setErrorMsg(entity, "用户密码不能为空");
        }
        if(user.contains(entity.getUserAccount())){
            setErrorMsg(entity, "用户账号系统已经存在");
        }
        if (StrUtil.isNotBlank(entity.getErrorMsg())) {
            errorList.add(entity);
        } else {
            MsgxUserEntity userEntity = BeanUtil.toBean(entity, MsgxUserEntity.class);
            userEntity.setUserId(IdUtil.fastSimpleUUID());
            //加盐
            String salt = RandomUtil.randomString(8);
            String base = entity.getUserPassword() + "/" + salt;
            String password = DigestUtils.md5DigestAsHex(base.getBytes());
            userEntity.setUserPassword(password);
            userEntity.setUserSalt(salt);
            userEntity.setUserAuthStartTime(date);
            userEntity.setUserAuthEndTime(DateUtil.offset(date, DateField.YEAR, 1));
            // 普通用户
            userEntity.setUserType(1);
            list.add(userEntity);
            // 达到BATCH_COUNT了，需要去存储一次数据库，防止数据几万条数据在内存，容易OOM
            if (list.size() >= batchCount) {
                saveData();
                // 存储完成清理 list
                list.clear();
            }
        }
    }

    public void setErrorMsg(MsgxUserExcelDto entity, String msg) {
        if (StrUtil.isNotBlank(entity.getErrorMsg())) {
            entity.setErrorMsg(entity.getErrorMsg() + "," + msg);
        } else {
            entity.setErrorMsg(msg);
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据，确保最后遗留的数据也存储到数据库
        saveData();
        log.info("所有数据解析完成！");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据，开始存储数据库！", list.size());
        try {
            msgxUserService.saveBatch(list);
        } catch (Exception e) {
            log.error("存储数据库失败！", e);
        }
        log.info("存储数据库成功！");
    }

    /**
     * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
     *
     * @param exception
     * @param context
     * @throws Exception
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        log.error("解析失败，但是继续解析下一行:{}", exception.getMessage());
        // 如果是某一个单元格的转换异常 能获取到具体行号
        // 如果要获取头的信息 配合invokeHeadMap使用
        Object currentRowAnalysisResult = context.readRowHolder().getCurrentRowAnalysisResult();
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
            log.error("第{}行，第{}列解析异常", excelDataConvertException.getRowIndex(), excelDataConvertException.getColumnIndex());
        }
    }

    /**
     * 导入结果数量
     *
     * @return
     */
    public Map<String, Object> getMapSize() {
        Map<String, Object> maps = new HashMap<>();
        maps.put("successSize", list.size());
        maps.put("errorSize", errorList.size());
        maps.put("allSize", list.size() + errorList.size());
        if (errorList.size() > 0) {
            MsgxUserExcelErrorDto errorDto = new MsgxUserExcelErrorDto();
            errorDto.setDate(DateUtil.date());
            errorDto.setId(IdUtil.fastSimpleUUID());
            errorDto.setExcelDto(errorList);
            UploadConstant.ERROR_UPLOAD.put(errorDto.getId(), errorDto);
            maps.put("errorKey", errorDto.getId());
        }
        return maps;
    }


    /**
     * 导入成功数量
     *
     * @return
     */
    public Integer getSuccessSize() {
        return list.size();
    }

    /**
     * 导入失败数量
     *
     * @return
     */
    public Integer getErrorSize() {
        return errorList.size();
    }
}
